package org.will.ddd.domain;

import org.will.ddd.eventing.IDomainEvent;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Created by EdifierWill on 2016/8/10.
 */
public class AggregateRoot<TAggregateRootId> implements IAggregateRoot{

    private int _version;
    private static final List<IDomainEvent> _emptyEvents = new ArrayList<IDomainEvent>();
    private Queue<IDomainEvent> _uncommittedEvents;
    protected TAggregateRootId _id;

    protected void setId(TAggregateRootId id) {
        this._id = id;
    }
    public TAggregateRootId getId()
    {
        return this._id;
    }

    /// <summary>Default constructor.
    /// </summary>
    protected AggregateRoot() {
        _uncommittedEvents = new ConcurrentLinkedQueue<IDomainEvent>();
    }

    /// <summary>Parameterized constructor.
    /// </summary>
    protected AggregateRoot(TAggregateRootId id) throws IllegalArgumentException
    {
        this();
        if (id == null)
        {
            throw new IllegalArgumentException("id");
        }
        _id = id;
    }

    /// <summary>Parameterized constructor.
    /// </summary>
    protected AggregateRoot(TAggregateRootId id, int version) throws Exception {
        this(id);
        if (version < 0)
        {
            throw new Exception(String.format("Version cannot small than zero, aggregateRootId: {0}, version: {1}", id, version));
        }
        _version = version;
    }

    public int Version()
    {
        return  _version;
    }

    public void setVersion(int version)
    {
        this._version = version;
    }

    /// <summary>Get all the changes of the aggregate root.
    /// </summary>
    /// <returns></returns>
    public Iterable<IDomainEvent> GetChanges()
    {
        if (_uncommittedEvents == null){
            return _emptyEvents;
        }

        return _uncommittedEvents;
    }

    /// <summary>Apply a domain event to the current aggregate root.
    /// </summary>
    /// <param name="domainEvent"></param>
    protected void ApplyEvent(IDomainEvent<TAggregateRootId> domainEvent) throws Exception
    {
        if (domainEvent == null)
        {
            throw new IllegalArgumentException("domainEvent");
        }
        if (domainEvent.getAggregateRootId().equals(null))
        {
            throw new Exception("Aggregate root id cannot be null.");
        }
        domainEvent.setAggregateRootId(this.getId());
        domainEvent.setVersion(_version + 1);
        AppendUncommittedEvent(domainEvent);
    }

    private void AppendUncommittedEvent(IDomainEvent<TAggregateRootId> domainEvent) throws UnsupportedOperationException
    {
        if (_uncommittedEvents == null)
        {
            _uncommittedEvents = new ConcurrentLinkedQueue<IDomainEvent>();
        }

        for(IDomainEvent event:_uncommittedEvents){
            if(event.getClass().equals(domainEvent.getClass())){
                throw new UnsupportedOperationException(String.format("Cannot apply duplicated domain event type: {0}, current aggregateRoot type: {1}, id: {2}", domainEvent.getClass().getName(), this.getClass().getName(), _id));
            }
        }
        _uncommittedEvents.offer(domainEvent);
    }

    /// <summary>Accept changes with new version.
    /// </summary>
    /// <param name="newVersion"></param>
    public void AcceptChanges(int newVersion) throws UnsupportedOperationException
    {
        if (_version + 1 != newVersion)
        {
            throw new UnsupportedOperationException (String.format("Cannot accept invalid version: {0}, expect version: {1}, current aggregateRoot type: {2}, id: {3}", newVersion, _version + 1, this.getClass().getName(), _id));
        }
        _version = newVersion;
        _uncommittedEvents.clear();
    }
}
